3. Abstract Factory


Posted by WayneCheng on 2021-01-10

關於 Abstract Factory 本篇將討論以下幾個問題

1. 關於 Abstract Factory

2. UML

3. 將 UML 轉為程式碼

4. 情境


測試環境:

OS:Windows 10
IDE:Visual Studio 2019


1. 關於 Abstract Factory

Provide an interface for creating families of related or dependent objects without specifying their concrete classes.

by Gang of Four

  • 提供一個用於創建相關或依賴物件家族(父類別 & 子類別)的接口,且無需指定其具體類別

Abstract Factory(抽象工廠)屬於創建型(Creational Patterns),當遇到需要取得兩個以上相互關聯的實體時,使用 Abstract Factory 來取得實體並將依賴邏輯處理後回傳結果,Abstract Factory 為 Factory Method 的加強版,同時使用 class 的數量也是加強版。

優點:

  • 符合 單一職責原則(Single Responsibility Principle)
  • 符合 開閉原則(Open Closed Principle)

缺點:

  • 會增加許多 class 造成程式複雜度增加

2. UML


Class 間關聯:

  • Product A1 / A2 繼承 AbstractProductA
  • Product B1 / B2 繼承 AbstractProductB
  • ConcreteFactory 1 / 2 繼承 AbstractFactory
  • Client 關聯 AbstractProduct A / B & AbstractFactory

Class:

  • Client:呼叫端,透過 Factory 取得多個 Product 實體,並處理其關聯邏輯
  • AbstractProduct:處理邏輯的抽象類別或介面
  • Product:處理邏輯實作
  • AbstractFactory:抽象工廠的抽象類別或介面
  • ConcreteFactory:抽象工廠實作,用來創建處理邏輯實體

3. 將 UML 轉為程式碼

處理邏輯介面

/// <summary>
/// 處理邏輯介面 A
/// </summary>
public interface IProductA
{
}

/// <summary>
/// 處理邏輯的介面 B
/// </summary>
public interface IProductB
{
    void Interact(IProductA a);
}

處理邏輯實作

/// <summary>
/// 處理邏輯介面 A 實作 A1
/// </summary>
public class ProductA1 : IProductA
{
}

/// <summary>
/// 處理邏輯介面 B 實作 B1
/// </summary>
class ProductB1 : IProductB
{
    public void Interact(IProductA a)
    {
        Console.WriteLine($"{this.GetType().Name} interacts with {a.GetType().Name}");
    }
}

/// <summary>
/// 處理輯介面 A 實作 A2
/// </summary>
public class ProductA2 : IProductA
{
}

/// <summary>
/// 處理邏輯介面 B 實作 B2
/// </summary>
class ProductB2 : IProductB
{
    public void Interact(IProductA a)
    {
        Console.WriteLine($"{this.GetType().Name} interacts with {a.GetType().Name}");
    }
}

抽象工廠的介面

/// <summary>
/// 抽象工廠介面
/// </summary>
public interface IFactory
{
    IProductA CreateProductA();
    IProductB CreateProductB();
}

抽象工廠實作

/// <summary>
/// 抽象工廠實作 1
/// </summary>
public class ConcreteFactory1 : IFactory
{
    public IProductA CreateProductA()
    {
        return new ProductA1();
    }
    public IProductB CreateProductB()
    {
        return new ProductB1();
    }
}

/// <summary>
/// 抽象工廠實作 2
/// </summary>
public class ConcreteFactory2 : IFactory
{
    public IProductA CreateProductA()
    {
        return new ProductA2();
    }
    public IProductB CreateProductB()
    {
        return new ProductB2();
    }
}

呼叫端,透過 factory 取得 Product 實作,並處理 Product 間依賴邏輯

/// <summary>
/// 呼叫端,透過 factory 取得 Product 實作,並處理 Product 間依賴邏輯
/// </summary>
public class Client
{
    private IProductA _productA;
    private IProductB _productB;

    public Client(IFactory factory)
    {
        _productB = factory.CreateProductB();
        _productA = factory.CreateProductA();
    }

    public void Run()
    {
        _productB.Interact(_productA);
    }
}
  1. 建立工廠 factory 分別傳入 client
  2. 透過 client 處理 product 間邏輯
static void Main(string[] args)
{
    Default.IFactory factory1 = new Default.ConcreteFactory1();
    Default.Client client1 = new Default.Client(factory1);
    client1.Run();

    Default.IFactory factory2 = new Default.ConcreteFactory2();
    Default.Client client2 = new Default.Client(factory2);
    client2.Run();

    Console.ReadLine();
}

執行結果

ProductB1 interacts with ProductA1
ProductB2 interacts with ProductA2

4. 情境

我們接到了一個付款且要依據付款方式打折的需求

  • 需要能支援現有的兩種付款方式(現金、ApplePay)
  • 要依據付款方式提供折扣
  • 且未來可能會有更多不同的付款方式

建立付款 & 折扣介面

/// <summary>
/// 付款方式介面
/// </summary>
public interface IPayment
{
    void Pay(int amount);
}

/// <summary>
/// 折扣介面
/// </summary>
public interface IDiscount
{
    void Interact(IPayment payment, int amount);
}

實作付款介面

/// <summary>
/// 實作付款介面:現金
/// </summary>
public class Cash : IPayment
{
    public void Pay(int amount)
    {
        Console.WriteLine($"使用 現金 付款 {amount} 元");
    }
}

/// <summary>
/// 實作付款介面:ApplePay
/// </summary>
public class ApplePay : IPayment
{
    public void Pay(int amount)
    {
        Console.WriteLine($"使用 ApplePay 付款 {amount} 元");
    }
}

實作折扣介面

/// <summary>
/// 實作折扣介面:現金
/// </summary>
class DiscountCash : IDiscount
{
    public void Interact(IPayment payment, int amount)
    {
        // 使用現金無折扣
        payment.Pay(amount);
    }
}

/// <summary>
/// 實作折扣介面:ApplePay
/// </summary>
class DiscountApplePay : IDiscount
{
    public void Interact(IPayment payment, int amount)
    {
        // 使用 ApplePay 打九折
        var newAmount = (int)(amount * 0.9);
        payment.Pay(newAmount);
    }
}

抽象工廠介面

/// <summary>
/// 抽象工廠介面
/// </summary>
public interface IFactory
{
    IPayment CreatePayment();
    IDiscount CreateDiscount();
}

分別實作「現金付款工廠」&「ApplePay 付款工廠」

/// <summary>
/// 現金付款工廠
/// </summary>
public class CashFactory : IFactory
{
    public IPayment CreatePayment()
    {
        return new Cash();
    }
    public IDiscount CreateDiscount()
    {
        return new DiscountCash();
    }
}

/// <summary>
/// ApplePay 付款工廠
/// </summary>
public class ApplePayFactory : IFactory
{
    public IPayment CreatePayment()
    {
        return new ApplePay();
    }
    public IDiscount CreateDiscount()
    {
        return new DiscountApplePay();
    }
}

呼叫端,依據傳入的工廠取得付款方式 & 折扣方式

/// <summary>
/// 取得付款 & 折扣實作,並可進行付款動作
/// </summary>
public class Client
{
    private IPayment _payment;
    private IDiscount _discount;

    public Client(IFactory factory)
    {
        _discount = factory.CreateDiscount();
        _payment = factory.CreatePayment();
    }

    public void Pay(int amount)
    {
        _discount.Interact(_payment, amount);
    }
}
  1. 建立工廠 cashFactory & applePayFactory 分別傳入 Client
  2. 透過 Client 處理付款邏輯 & 折扣邏輯
static void Main(string[] args)
{
    Situation.IFactory cashFactory = new Situation.CashFactory();
    Situation.Client cashClient = new Situation.Client(cashFactory);
    cashClient.Pay(100);

    Situation.IFactory applePayFactory = new Situation.ApplePayFactory();
    Situation.Client applePayClient = new Situation.Client(applePayFactory);
    applePayClient.Pay(100);

    Console.ReadLine();
}

執行結果

使用 現金 付款 100 元
使用 ApplePay 付款 90 元

完整程式碼

GitHub:Creational_02_AbstractFactory


總結

Abstract Factory 比 Factory Method 複雜些,滿難一眼就理解其間的差異,在此建議可以試著先將 UML 內 class 之間的關聯釐清後,依據自己的理解將 UML 畫出來,藉此加深對各 Design Pattern 的印象。


參考資料

  1. Design Patterns
  2. 大話設計模式
  3. dofactory
  4. Refactoring.Guru

新手上路,若有錯誤還請告知,謝謝


#designpattern #CSharp







Related Posts

關於 Cookie !!

關於 Cookie !!

What Type of Laser Engraving Machine Should be Used for Stainless Steel Engraving?

What Type of Laser Engraving Machine Should be Used for Stainless Steel Engraving?

MTR04_1004

MTR04_1004


Comments